#! /bin/env perl
#
# ReleaseTool
#

use strict;
use DirHandle;
use FileHandle;
use Encode qw/encode decode/;
use utf8;

# REMOVE_COMMENT_xxxx 対象となるファイルの拡張子(ファイル名後方一致)
my( @A_ext_list ) = qw/
	.C
	.H
	.S
	.INC
	.OPT
	.TXT
	.BAT
	.SCAT
	.VIA
	.CFG
	.DOXY
	.MK
	.SH
	Makefile
/;


# デバッグログフラグ
my( $B_dbglog ) = undef;
#my( $B_dbglog ) = 1;
#



sub main {
	binmode STDOUT, ':encoding(utf8)';
	binmode STDERR, ':encoding(utf8)';

	if( scalar(@ARGV) == 0 ) {
		print_usage();
		exit 64;
	}


	my( $s_mode ) = $ARGV[0];

	if( $s_mode eq 'remove_ini' ) {
		remove_ini_main();
	}
	elsif( $s_mode eq 'remove_comment' ) {
		remove_comment_main();
	}
	elsif( $s_mode eq 'search_str' ) {
		search_str_main();
	}
	else {
		printf( STDERR "Unknown option.\n", $s_mode );
		print_usage();
		exit 64;
	}

	exit 0;
}

sub print_usage {
	my( $s_cmd_name ) = $0;
	$s_cmd_name =~ s/^.*[\\\/]([^\\\/]+)$/$1/;

	my( $a_usage_str ) = [
		'remove_ini     <ini path>',
		'remove_comment <target path> <remove strings> <release|remove>',
		'search_str     <target path> <search strings>',
	];

	foreach my $s_usage_str ( @$a_usage_str ) {
		printf( STDERR "Usage: %s %s\n", $s_cmd_name, $s_usage_str );
	}
}

sub remove_ini_main {
	my( $h_arg ) = {
		's_target_path'    => $ARGV[1],
		'b_recursive'      => 1,	# true:1 false:undef
	};

	# iniファイルをパースして内容を受け取る
	my( $a_ini_dat_array ) = remove_ini_parse( $h_arg->{'s_target_path'} );
	unless( defined($a_ini_dat_array) ) {
		return;
	}



	# iniファイルの内容を表示
	if( defined($B_dbglog) ) {
		foreach my $h_ini_dat (@$a_ini_dat_array) {
			printf( "IP name: %s\n", $h_ini_dat->{'IP name'} );
			printf( "Target path:\n\t" );
			print( join( "\n\t", @{$h_ini_dat->{'a_target_path_array'}} ) . "\n" );
			printf( "Unlink path:\n\t" );
			print( join( "\n\t", @{$h_ini_dat->{'a_unlink_path_array'}} ) . "\n" );
		}
	}



	# REMOVE_xxxを処理
	foreach my $h_ini_dat (@$a_ini_dat_array) {
		$B_dbglog && printf( "### %s\n", $h_ini_dat->{'IP name'} );

		foreach my $s_unlink_path (@{$h_ini_dat->{'a_unlink_path_array'}}) {
			$B_dbglog && printf( "   ##> %s\n", $s_unlink_path );
			printf( "\"%s\": %s file/directory %s ...\n", $h_ini_dat->{'IP name'}, $h_ini_dat->{'b_release_remove'}, $s_unlink_path );

			if( $h_ini_dat->{'b_release_remove'} eq 'release' ) {
				next;
			}

			if( -d $s_unlink_path ) {
				remove_dir_recursive( $s_unlink_path );
			}
			else {
				unlink( $s_unlink_path );
			}
		}
	}



	# カレントディレクトリから一致するファイルのパス文字列を配列で得る
	my( $s_ext_regexp );
	$s_ext_regexp = '(' . join( '|', @A_ext_list ) . ')';
	$s_ext_regexp =~ s/\./\\./g;

	my( @as_all_file_path ) = search_file( '.', $s_ext_regexp, $h_arg->{'b_recursive'} );
	# 行等の"./"を削除
	grep( $_ =~ s/^\.\///o, @as_all_file_path );



	# REMOVE_COMMENT_xxxを処理
	$h_arg->{'s_line_sep'} = ':lf';

	foreach my $h_ini_dat (@$a_ini_dat_array) {
		$B_dbglog && printf( "*** %s %s\n", $h_ini_dat->{'IP name'}, $h_ini_dat->{'s_search_str'} );
		$h_arg->{'s_search_str'} = $h_ini_dat->{'s_search_str'};
		$h_arg->{'b_release_remove'} = $h_ini_dat->{'b_release_remove'};

		foreach my $s_target_path (@{$h_ini_dat->{'a_target_path_array'}}) {
			$s_target_path =~ s/\\/\//g;
			$B_dbglog && printf( "  **> %s\n", $s_target_path );

			printf( "\"%s\": %s comment %s ...\n", $h_ini_dat->{'IP name'}, $h_ini_dat->{'b_release_remove'}, $s_target_path );

			if( -d $s_target_path ) {
				# ディレクトリなら、@as_all_file_pathから一致するパスを取得して1ファイルずつコメント削除処理を実施
				foreach my $s_path ( grep( /^$s_target_path/, @as_all_file_path ) ) {
					$B_dbglog && printf( "  **> %s\n", $s_path );
					remove_comment( $s_path, $h_arg );
				}
			}
			if( -f $s_target_path ) {
				# 通常ファイル
				remove_comment( $s_target_path, $h_arg );
			}
		}
	}
}

sub remove_ini_parse {
	my( $s_ini_file_path ) = @_;

	my( $a_ini_dat_array ) = [
	];

	my( $h_ini_dat ) = undef;
	my( $linebuf );
	my( $fh_in );

	$fh_in = new FileHandle;
	unless( $fh_in->open( "< $s_ini_file_path" ) ) {
		$B_dbglog && printf( "File open error: %s\n", $s_ini_file_path );
		return undef;
	}


	while( defined($linebuf = $fh_in->getline) ) {
		$linebuf =~ s/[\r\n]+$//;	# Remove Cr or/and Lf


		if( $linebuf =~ /^(REMOVE_[a-zA-Z0-9_]+)="([^"]*)",([01])\b/o ) {
			$h_ini_dat = {
				# 's_search_str'
				# 'IP name'
				# 'b_release_remove'
				# 'a_target_path_array'
				# 'a_unlink_path_array'
			};
			$h_ini_dat->{'s_search_str'} = $1;
			$h_ini_dat->{'IP name'} = $2;
			if( $3 == 0 ) {
				$h_ini_dat->{'b_release_remove'} = 'remove';
			}
			else {
				$h_ini_dat->{'b_release_remove'} = 'release';
			}
			$h_ini_dat->{'a_target_path_array'} = [];
			$h_ini_dat->{'a_unlink_path_array'} = [];

			push( @$a_ini_dat_array, $h_ini_dat );
			next;
		}

		unless( defined($h_ini_dat) ) {
			next;
		}

		if( $linebuf =~ /^REMOVE_(COMMENT_DIR|COMMENT_FILE)=(.+)$/o ) {
				push( @{$h_ini_dat->{'a_target_path_array'}}, $2 );
				next;
		}
		if( $linebuf =~ /^REMOVE_(DIR|FILE)=(.+)$/o ) {
				push( @{$h_ini_dat->{'a_unlink_path_array'}}, $2 );
				next;
		}
	}

	$fh_in->close;

	return $a_ini_dat_array;
}

sub remove_comment_main {
	my( $h_arg ) = {
		's_target_path'    => $ARGV[1],
		'b_recursive'      => 1,	# true:1 false:undef
		's_search_str'     => $ARGV[2],
		'b_release_remove' => $ARGV[3],	# "release", "remove"
	};

	$h_arg->{'s_line_sep'} = ':lf';

	my( $s_ext_regexp );
	$s_ext_regexp = '(' . join( '|', @A_ext_list ) . ')';
	$s_ext_regexp =~ s/\./\\./g;

	my( @as_all_file_path );
	if( -d $h_arg->{'s_target_path'} ) {
		@as_all_file_path = search_file( $h_arg->{'s_target_path'}, $s_ext_regexp, $h_arg->{'b_recursive'} );
	}
	else {
		@as_all_file_path = ( $h_arg->{'s_target_path'} );
	}

	foreach my $s_path ( @as_all_file_path ) {
		$B_dbglog && printf( "%s\n", $s_path );
		remove_comment( $s_path, $h_arg );
	}
}

sub remove_comment {
	my( $s_in_file_path, $h_arg ) = @_;

	my( $s_out_file_path ) = $s_in_file_path . '.tmp';
	my( $linebuf );
	my( $b_is_matched ) = undef;
	my( $fh_in );
	my( $fh_out ) = undef;
	my( $out_linebuf ) = '';

	$fh_in = new FileHandle;
	unless( $fh_in->open( "< $s_in_file_path" ) ) {
		printf( "File open error: %s\n", $s_in_file_path );
		return undef;
	}
#	binmode $fh_in, $h_arg->{'s_encoding'};
	binmode $fh_in, $h_arg->{'s_line_sep'};



	while( defined($linebuf = $fh_in->getline) ) {
		$linebuf =~ s/[\r\n]+$//;	# Remove Cr or/and Lf

		unless( $linebuf =~ /$h_arg->{'s_search_str'}/ ) {
			if( $b_is_matched == 1 ) {
				unless( defined($fh_out) ) {
					$fh_out = new FileHandle;
					unless( $fh_out->open( "> $s_out_file_path" ) ) {
						printf( "File open error: %s\n", $s_out_file_path );
						return undef;
					}
#					binmode $fh_out, $h_arg->{'s_encoding'};
					binmode $fh_out, $h_arg->{'s_line_sep'};
					if( $out_linebuf ne '' ) {
						$fh_out->print( $out_linebuf );
						$out_linebuf = '';
					}
				}
				$fh_out->print( $linebuf . "\n" );
			}
			else {
				$out_linebuf .= $linebuf . "\n";
			}
			next;
		}

		# Inside between $h_arg->{'s_search_str'}
		$b_is_matched = 1;
		if( $h_arg->{'b_release_remove'} eq 'remove' ) {
			while( defined($linebuf = $fh_in->getline) ) {
				if( $linebuf =~ /$h_arg->{'s_search_str'}/ ) {
					last;
				}
			}
		}
	}

	$fh_in->close;

	unless( defined($b_is_matched) ) {
		return;
	}

	$fh_out->close;
	unlink( $s_in_file_path );
	rename( $s_out_file_path, $s_in_file_path );
}

sub search_str_main {
	my( $h_arg ) = {
		's_target_path'    => $ARGV[1],
		'b_recursive'      => 1,	# true:1 false:undef
		's_search_str'     => $ARGV[2],
	};

	$h_arg->{'s_line_sep'} = ':lf';

	my( $s_ext_regexp );
	$s_ext_regexp = '(' . join( '|', @A_ext_list ) . ')';
	$s_ext_regexp =~ s/\./\\./g;

	my( @as_all_file_path );
	if( -d $h_arg->{'s_target_path'} ) {
		@as_all_file_path = search_file( $h_arg->{'s_target_path'}, $s_ext_regexp, $h_arg->{'b_recursive'} );
	}
	else {
		@as_all_file_path = ( $h_arg->{'s_target_path'} );
	}

	foreach my $s_path ( @as_all_file_path ) {
		search_str( $s_path, $h_arg );
	}
}

sub search_str {
	my( $s_in_file_path, $h_arg ) = @_;

	my( $linebuf );
	my( $fh_in );

	$fh_in = new FileHandle;
	unless( $fh_in->open( "< $s_in_file_path" ) ) {
		printf( "File open error: %s\n", $s_in_file_path );
		return undef;
	}
#	binmode $fh_in, $h_arg->{'s_encoding'};
	binmode $fh_in, $h_arg->{'s_line_sep'};


	while( defined($linebuf = $fh_in->getline) ) {
		$linebuf =~ s/[\r\n]+$//;	# Remove Cr or/and Lf

		if( $linebuf =~ /$h_arg->{'s_search_str'}/ ) {
			printf( "%s,%d,%s\n", $s_in_file_path, $fh_in->input_line_number, $linebuf );
			next;
		}
	}

	$fh_in->close;
}

##
## 指定ディレクトリ配下のファイルのパス一覧を取得する
## (再帰コールされる)
##
## in: $s_search_dir : 検索対象とするディレクトリ
## in: $s_search_file_suffix : 検索対象とするファイルの拡張子("."も入れる。指定しない場合は全て)
## out: @as_all_file_path : ファイルパスの配列

sub search_file {
	my( $s_search_dir, $s_search_file_suffix, $b_is_recursive ) = @_;

	# $s_search_dirにあるファイル/ディレクトリ一覧取得
	my @as_all_dir_path;
	my @as_all_file_path;


	my @asl_all_path = &get_dir_entry_list( $s_search_dir );

	# ディレクトリ一覧作成
	@as_all_dir_path = grep( -d $_, @asl_all_path );
	# ファイル一覧作成
	@as_all_file_path = grep( ! -d $_, @asl_all_path );


	# カレントディレクトリ、親ディレクトリを除く
	@as_all_dir_path = grep( $_ !~ /\/\.\.?$/o, @as_all_dir_path );

	# 拡張子が合致するファイルを検索し戻り値に設定
	if( length($s_search_file_suffix) > 0 ) {
		@as_all_file_path = grep( /${s_search_file_suffix}$/i, @as_all_file_path );
	}


	# ディレクトリ内のファイルを検索
	if( $b_is_recursive != 0 ) {
		foreach my $s_file_path ( @as_all_dir_path ) {
			# $s_dir_pathディレクトリの内部を探索(再帰コール)
			# 得られたCソースファイル一覧を配列に追加
			push( @as_all_file_path, &search_file( $s_file_path, $s_search_file_suffix, $b_is_recursive ) );
		}
	}


	return @as_all_file_path;
}

## ディレクトリにあるエントリの一覧をパス付の形で得る
##
## in: $s_dir_path ディレクトリパス
## ret: @as_file_path ディレクトエントリのパス配列
sub get_dir_entry_list {
	my( $s_dir_path ) = @_;

	# ディレクトリオープン
	my( $dh ) = new DirHandle( $s_dir_path );
	unless( defined $dh ) {
		return undef;
	}

	# ディレクトリ内のエントリ一覧を取得
	my @as_file_path =  map "$s_dir_path/$_", $dh->read;

	return @as_file_path;
}


## 指定ディレクトリを削除する
## (再帰コールされる)
##
## in: $s_remove_dir : 検索対象とするディレクトリ
## ret: 成功:1 失敗:0
##
sub remove_dir_recursive {
	my( $s_remove_dir ) = @_;
	my( $i_ret ) = 0;

	# $s_remove_dirにあるファイル/ディレクトリ一覧取得
	my @as_all_dir_path;
	my @as_all_file_path;
	my @asl_all_path = &get_dir_entry_list( $s_remove_dir );

	# ディレクトリ一覧作成
	@as_all_dir_path = grep( -d $_, @asl_all_path );
	# ファイル一覧作成
	@as_all_file_path = grep( ! -d $_, @asl_all_path );

	# カレントディレクトリ、親ディレクトリを取り除く
	@as_all_dir_path = grep( $_ !~ /\/\.\.?$/o, @as_all_dir_path );


	# ファイルを全て削除
	$i_ret += unlink @as_all_file_path;


	# ディレクトリ内の全てのエントリを削除してからディレクトリを削除
	map { &remove_dir_recursive( $_ ); } @as_all_dir_path;


	# 引数で指定されたディレクトリ削除
	$i_ret += rmdir( $s_remove_dir );

	return ($i_ret != 0)?(1):(0);
}

&main;

